package com.example.inperson;

import java.util.Date;

import android.app.Activity;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

/**
 * @author Alex
 * 
 *         2014-03-27 Start off with a simple compass to point North, then
 *         adjust so the compass will point to a particular set of GPS
 *         coordinates. However, if pointing to a set of coordinates those
 *         coordinates may update in real-time. Right now unknown how often the
 *         coordinates will adjust.
 * 
 *         Image of a compass is stored in \res\drawable-mdpi Name of the image:
 *         img_compass.jpg
 * 
 *         Delete when this class is complete.
 * 
 *         Primary code from:
 *         http://www.javacodegeeks.com/2013/09/android-compass
 *         -code-example.html
 * 
 *         Trig formula: http://mathforum.org/library/drmath/view/55417.html
 * 
 *         ++++++++++++++++++++ 2014-03-28 There are also various subclasses
 *         devoted to adding functionality. Distance and bearing calculations
 *         will also be done in this class.
 * 
 *         This compass needs to be able to determine: -Distance between source
 *         and target -Compass heading between source and target -Update this
 *         information in real-time
 * 
 *         This means the compass needs to be receiving coordinate information.
 *         As of writing this (8:35am), John is still working on a WiFi
 *         Broadcast/Receive connection. (Does this mean the phone is unable to
 *         use the built-in cell-tower GPS?)
 * 
 *         To access GPS the app must make a request to the Android device in
 *         order to try. For now we will assume the user will always say yes.
 *         However, we will need to add error dialogue if the user says "YES" to
 *         find the target but says "NO" when it comes to using location
 *         services.
 * 
 *         Draw upon the GPSTracker.java in order to determine current location.
 */

public class CompassActivity extends Activity implements SensorEventListener {
	// tracks source heading in degrees. onSensorChanged() will update this
	private float sourceDegree;
	
	private long lastUpdated;
	
	private double sourceLongX = 0.0;
	private double sourceLatY = 0.0;
	
	//currently set by hand
	private double destLongX = -157.8163792;
	private double destLatY = 21.2992189;
	
	private double distance = 0.1234;
	
	private Handler mHandler;

	Button btnShowLocation;

	// GPSTracker class
	GPSTracker gps;

	// Class variables defined and used by the example code
	// define the display assembly compass picture
	private ImageView image; // do a HUD-display image?

	// record the compass picture angle turned
	private float currentDegree = 0f;
	
	boolean initialized = false;
	boolean hasUpdatedGPS = false;

	// device sensor manager
	private SensorManager mSensorManager;

	private TextView tvHeading;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		Log.d("CompassActivity", "onCreate()");
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_compass);

		image = (ImageView) findViewById(R.id.imageViewCompass);

		// TextView which displays the current heading
		tvHeading = (TextView) findViewById(R.id.tvHeading);

		// initialize Android device sensor capabilities
		mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
		
		// Slap out a quick GPS update.
		gps = new GPSTracker(CompassActivity.this);
		if (gps.canGetLocation()) {
			Log.d("lol", "Initializing GPS");
			hasUpdatedGPS = true;
			sourceLatY = gps.getLatitude();
			sourceLongX = gps.getLongitude();
			double tempx = (destLongX - sourceLongX)*(destLongX - sourceLongX);
			double tempy = (destLatY - sourceLatY)*(destLatY - sourceLatY);
			distance = (Math.sqrt(tempx + tempy) * (10000 / 90)) * 3280.34; 
			
			lastUpdated = (new Date()).getTime();
		} else { Log.d("lol", "Unable to perform on-launch GPS initialization."); }
		
		mHandler = new Handler();
/*
		// show location button click event
		btnShowLocation.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View arg0) {
				// check if GPS enabled
				if (gps.canGetLocation()) {
					hasUpdatedGPS = true;
					Log.d("CompassActivity", "GPS can get current location");
					sourceLatY = gps.getLatitude();
					sourceLongX = gps.getLongitude();
					
					//distance calculation goes here
					double tempx = (destLongX - sourceLongX)*(destLongX - sourceLongX);
					double tempy = (destLatY - sourceLatY)*(destLatY - sourceLatY);
					
					//10000 / 90 = degrees to kilometers
					//3280.34 = kilometers to feet
					distance = (Math.sqrt(tempx + tempy) * (10000 / 90)) * 3280.34; 
					
					lastUpdated = (new Date()).getTime();

					// \n is for new line
					Toast.makeText(
							getApplicationContext(),
							"Your Location is - \nLat: " + sourceLatY
									+ "\nLong: " + sourceLongX 
									+ "\nYour destination is - \nLat: " + destLatY
									+ "\nLong: " + destLongX
									+ "\nDistance: " + distance + " feet", Toast.LENGTH_LONG)
							.show();
				} else {
					// can't get location
					// GPS or Network is not enabled
					// Ask user to enable GPS/network in settings
					Log.d("CompassActivity", "GPS CANNOT get current location");
					Toast.makeText(
							getApplicationContext(),
							"Nope, GPS cannot get location: - \nLat: " + 1.234
									+ "\nLong: " + 5.678, Toast.LENGTH_LONG)
							.show();
					gps.showSettingsAlert();
					Log.d("CompassActivity",
							"showSettingsAlert() did NOT function properly");
				}

			}
		});
		*/
		
		startRepeatingEvent();
	}// end of onCreate()

	@Override
	protected void onResume() {
		super.onResume();
		// for the system's orientation sensor registered listeners
		Log.d("CompassActivity", "CompassActivity resumed");
		mSensorManager.registerListener(this,
				mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION),
				SensorManager.SENSOR_DELAY_GAME);
		startRepeatingEvent();
	}

	@Override
	protected void onPause() {
		super.onPause();
		// stops the listener to save battery
		mSensorManager.unregisterListener(this);
		stopRepeatingEvent();
	}
	
	Runnable gpsUpdater = new Runnable() {
		@Override
		public void run() {
			double latcheck = sourceLatY;
			double longcheck = sourceLongX;
			
			// Update the GPS.
			sourceLatY = gps.getLatitude();
			sourceLongX = gps.getLongitude();
			
			//distance calculation goes here
			double tempx = (destLongX - sourceLongX)*(destLongX - sourceLongX);
			double tempy = (destLatY - sourceLatY)*(destLatY - sourceLatY);
			
			//10000 / 90 = degrees to kilometers
			//3280.34 = kilometers to feet
			distance = (Math.sqrt(tempx + tempy) * (10000 / 90)) * 3280.34;
			
			// Re-call this updater.
			mHandler.postDelayed(gpsUpdater, 200);
			
			Log.d("lol", "Update pulse.");
			if (latcheck != sourceLatY || longcheck != sourceLongX) {
				Log.d("lol", "Update succeeded.");
				lastUpdated = (new Date()).getTime();
			}
		}
	};
	
	public void startRepeatingEvent() {
		mHandler.postDelayed(gpsUpdater, 200);
	}
	
	public void stopRepeatingEvent() {
		mHandler.removeCallbacks(gpsUpdater);
	}

	/**
	 * Probably the most important piece of the code here. This method
	 * determines the new heading of the compass upon detecting a change in the
	 * source motion sensor.
	 * 
	 * How will we integrate distance into this sensor environment?
	 * 
	 */

	@Override
	public void onSensorChanged(SensorEvent event) {
		//the sensor changes because the person holding the device is moving
		//re-calculate bearing and re-calculate distance upon sensorevent (which is all the time)

		// get the angle around the z-axis rotated
		sourceDegree = Math.round(event.values[0]);
		
		//on first execution, currentDegree = 0
		//Log.d("currentDegree", Float.toString(currentDegree));
		//Log.d("sourceDegree", Float.toString(sourceDegree));

		//calculate heading based on knowledge of distance, latitude and longitude
		float targetDegree = Math.round((float) finalBearing(sourceLatY, sourceLongX, destLatY, destLongX));
		//target bearing is measured in degrees
		
		String dots = "";
		for (int i = 0; i < (((new Date()).getTime() - lastUpdated) / 5000); i++) { dots += "."; }
		
		//sourceDegree = the current heading
		tvHeading.setText((int) distance + " ft until you meet your new friend!" + (dots.length() > 0 ? "\r\n(Refreshing" + dots + ")" : ""));
		

		if (!initialized) {
			Log.d("lol", "Not initialized yet.");
			if (hasUpdatedGPS) {
				Log.d("lol", "Now initialized.");
				RotateAnimation ra = new RotateAnimation(currentDegree, -targetDegree, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
						0.5f);
				ra.setDuration(210);
				// set the animation after the end of the reservation status
				ra.setFillAfter(true);
				// start the animation
				image.startAnimation(ra);
				initialized = true;
				return;
			} else { Log.d("lol", "Cannot initialize yet - no GPS resolution."); }
		}
		
		// create a rotation animation (reverse turn degree degrees)
		//remember, first execution currentDegree will equal 0
		//sourceDegree is calculated by the actual sensor in the phone and will mostly likely be a number > 0
		RotateAnimation ra = new RotateAnimation(currentDegree, (targetDegree - sourceDegree),
				Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
				0.5f);
		// how long the animation will take place
		ra.setDuration(210);
		// set the animation after the end of the reservation status
		ra.setFillAfter(true);
		// start the animation
		image.startAnimation(ra);
		
		Log.d("lol", "CD: " + currentDegree + " SD: " + sourceDegree + " TD: " + targetDegree);
		
		currentDegree = (targetDegree - sourceDegree);
		

	}

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		// stub
	}

	/**
	 * HELPER METHODS: Make these methods private so other classes cannot access
	 * them.
	 * 
	 * http://software.intel.com/en-us/blogs/2012/11/30/calculating-a-bearing-between-points-in-location-aware-apps
	 */
	
	public double trueBearing(double lat1, double long1, double lat2, double long2)  {
		double degToRad = Math.PI / 180.0;
		double phi1 = lat1 * degToRad;
		double phi2 = lat2 * degToRad;
		double lam1 = long1 * degToRad;
		double lam2 = long2 * degToRad;
		
		return Math.atan2(Math.sin(lam2-lam1)*Math.cos(phi2),
                Math.cos(phi1)*Math.sin(phi2) - Math.sin(phi1)*Math.cos(phi2)*Math.cos(lam2-lam1)
            ) * 180/Math.PI;

	}  //end of trueBearing
	
	public double initialBearing(double lat1, double long1, double lat2, double long2)  {
		return (trueBearing(lat1, long1, lat2, long2) + 360.0) % 360;
	}
	
	public double finalBearing(double lat1, double long1, double lat2, double long2)  {
		return (trueBearing(lat2, long2, lat1, long1) + 180.0) % 360;
	}
	

}
